package com.crazy.base.starter.transaction.configuration;

import com.crazy.base.starter.transaction.properties.TransactionProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@ConditionalOnProperty(prefix = "base.transaction.rdb", name = "enabled", havingValue = "true")
@EnableConfigurationProperties({TransactionProperties.class})
@AutoConfiguration
@RequiredArgsConstructor
public class PointcutTransactionAutoConfiguration {

    private final TransactionProperties transactionProperties;

    @Bean("baseTransactionManager")
    public TransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    public TransactionInterceptor baseTxAdvice(TransactionManager transactionManager) {
        //事务管理规则，声明具备事务管理的方法名
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        //只读事务，不做更新操作
        RuleBasedTransactionAttribute readOnly = new RuleBasedTransactionAttribute();
        readOnly.setReadOnly(true);
        readOnly.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
        //当前存在事务就使用当前事务，当前不存在事务就创建一个新的事务
        RuleBasedTransactionAttribute required = new RuleBasedTransactionAttribute();
        //抛出异常后执行切点回滚,这边你可以更换异常的类型
        required.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
        //PROPAGATION_REQUIRED:事务隔离性为1，若当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务。这是默认值
        required.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        //设置事务失效时间，如果超过5秒，则回滚事务
        required.setTimeout(transactionProperties.getMethodTimeOut());
        Map<String, TransactionAttribute> attributesMap = new HashMap<>(30);
        String[] requiredPrefixArrays = transactionProperties.parseRequiredPrefix();
        //设置增删改上传等使用事务
        for (String prefix : requiredPrefixArrays) {
            attributesMap.put(prefix, required);
        }
        //查询开启只读
        String[] readonlyPrefixArrays = transactionProperties.parseReadonlyPrefix();
        for (String prefix : readonlyPrefixArrays) {
            attributesMap.put(prefix, readOnly);
        }
        source.setNameMap(attributesMap);
        return new TransactionInterceptor(transactionManager, source);
    }

    /**
     * 设置切面=切点pointcut+通知TxAdvice
     */
    @Bean("baseTxAdvisor")
    public Advisor txAdviceAdvisor(@Qualifier("baseTransactionManager") TransactionManager transactionManager) {
        /* 声明切点的面：切面就是通知和切入点的结合。通知和切入点共同定义了关于切面的全部内容——它的功能、在何时和何地完成其功能*/
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        /*声明和设置需要拦截的方法,用切点语言描写*/
        pointcut.setExpression(transactionProperties.getPointcutExpression());
        /*设置切面=切点pointcut+通知TxAdvice*/
        return new DefaultPointcutAdvisor(pointcut, baseTxAdvice(transactionManager));
    }

}
